#include "render_system/texture_2d.h"
#include <boost/lexical_cast.hpp>

#include "render_system/gl_header.h"

#include "utility.h"

Texture2D::Texture2D()
    : _uID(0)
    , _uWidth(0)
    , _uHeight(0)
    , _pixelFormat()
    , _pData()
    , _activeID(0)
{

}


Texture2D::~Texture2D()
{
    unbind();
    if (0 != _uID)
    {
        glDeleteTextures(1, &_uID);
        _uID = 0;
    }
}


void Texture2D::read(
    const size_t uWidth,
    const size_t uHeight,
    const PixelFormat& pixelformat,
    const void* pData)
{
    try
    {
        size_t uSizeInBytes = uWidth * uHeight;
        if (1 > uSizeInBytes)
        {
            throw std::exception("input size too small.");
        }
        uSizeInBytes *= pixelformat.bytes();

        _pData = shared_array(new char[uSizeInBytes]);
        memcpy(_pData.get(), pData, uSizeInBytes);

        _uWidth = uWidth;
        _uHeight = uHeight;
        _pixelFormat = pixelformat;

        _copy2gpu();
    }
    catch(const std::bad_alloc&)
    {
        std::string err("fail to malloc texture buffer for internal storage.");
        Log::error(err.c_str());
        throw std::exception(err.c_str());
    }
    catch(const std::exception& e)
    {
        Log::error(e.what());
        throw;
    }

}


PixelFormat Texture2D::pixelFormat() const
{
    return _pixelFormat;
}


size_t Texture2D::ID() const
{
    return _uID;
}

size_t Texture2D::width() const
{
    return _uWidth;
}

size_t Texture2D::height() const
{
    return _uHeight;
}

size_t Texture2D::glFormat() const
{
    return _pixelFormat.glFormat();
}

size_t Texture2D::glConponentType() const
{
    return _pixelFormat.glStorageType();
}

size_t Texture2D::channels() const
{
    return _pixelFormat.channels();
}


void Texture2D::setActiveID( const size_t activeID )
{
    try
    {
        if (31 < activeID)
        {
            std::string err("active texture ID must be in range 0 to 31, but your ID is ");
            err += boost::lexical_cast<std::string>(activeID);
            throw std::exception(err.c_str());
        }

        _activeID = activeID + GL_TEXTURE0;
    }
    catch(const std::exception& e)
    {
        Log::error(e.what());
        throw;
    }
}


void Texture2D::active()
{
    if ((GL_TEXTURE0 <= _activeID) && (GL_TEXTURE31 >= _activeID))
    {
        glActiveTexture(_activeID);
        bind();
    }
}


void Texture2D::deActive()
{
    if ((GL_TEXTURE0 <= _activeID) && (GL_TEXTURE31 >= _activeID))
    {
        glActiveTexture(0);
        unbind();
    }
}


void Texture2D::bind()
{
    if(0 != _uID)
    {
        glBindTexture(GL_TEXTURE_2D, _uID);
    }
}


void Texture2D::unbind()
{
    if(0 != _uID)
    {
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}


void Texture2D::_copy2gpu()
{
    try
    {
        // creating a texture object
        GLuint id;
        glGenTextures(1, &id);
        if (0 == id)
        {
            throw std::exception("fail to genereate tenxture object.");
        }
        _uID = id;

        glBindTexture(GL_TEXTURE_2D, _uID);

        // Copy file to OpenGL
        glTexImage2D(
            GL_TEXTURE_2D,                   // target
            0,                               // level of detail
            _pixelFormat.glInternalFormat(), // internal format
            _uWidth,                         // width
            _uHeight,                        // height
            0,                               // border: must be 0
            _pixelFormat.glFormat(),         // format
            _pixelFormat.glStorageType(),    // pixel data type
            _pData.get());                   // data pointer (in memory)

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        glBindTexture(GL_TEXTURE_2D, 0);    // unbind
    }
    catch(const std::exception& e)
    {
        Log::error(e.what());
        throw;
    }
}

